/*
 * pcx.c
 *
 * Read a 24bit color or 256 paletted compressed version 5 .pcx file.
 */

#include "pcx.h"

#include <lobi/fm.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>



/* images are stored as rgb byte triplets without padding */
typedef struct
{
	int		width, height;
	void*	pixels;
} rgb_image;

static rgb_image* read_pcx_file(struct fm_t* fm);

/* handle optional byte swapping from header data */
#define MAKE_INT(hi,lo) (((hi)<<8)|(lo))


int image_sread_pcx(const char* data, unsigned len, struct image_t* destination_image)
{
	struct fm_t fm;
	rgb_image* img;
	
	fm_open_mem((char*)data,len,&fm); // open file-or-memory stream with memory buffer
	img = read_pcx_file(&fm);

	if (!img)
		return 1;
	
	image_free(destination_image);

	destination_image->width = img->width;
	destination_image->height = img->height;

	destination_image->num_mipmaps = 0;
	destination_image->bytes_per_pixel = 3;
	destination_image->num_channels = 3;


	/* okay, this pcx reader doesnt turn the image right side up */
	{
		int row;
		char *s, *t;
		destination_image->pixels = malloc(img->width*img->height*3);

		s = img->pixels; 
		t = destination_image->pixels+(img->height-1)*3*img->width;
		for (row = 0; row < img->height; row++)
		{
			memcpy(t,s,img->width*3);
			s += img->width*3;
			t -= img->width*3;
		}
	}

	free(img->pixels);
	free(img);

	return 0;
}



typedef struct
{
	unsigned char	magic;
	unsigned char	version;
	unsigned char	encoding;
	unsigned char	bits_per_pixel;
	unsigned char	xmin_lo,xmin_hi,
					ymin_lo,ymin_hi,
					xmax_lo,xmax_hi,
					ymax_lo,ymax_hi;
	unsigned char	pad_dpi[4];
	unsigned char	pad_pal[16*3];
	unsigned char	pad_res;
	unsigned char	planes;
	unsigned char	bytes_per_line_lo, bytes_per_line_hi;
	unsigned char	pad_palinfo_lo, pad_palinfo_hi;
	unsigned char	pad_filler[58];
} pcx_header;


static int readpixels_256pal(struct fm_t* fp,rgb_image* img, int bytes_per_line);
static int readpixels_24bit(struct fm_t* fp,rgb_image* img, int bytes_per_line);


rgb_image* read_pcx_file(struct fm_t* fm)
{
	rgb_image* img = 0;
	pcx_header hdr;
	int xsize, ysize, bytes_per_line, scanline_size;

	if (!fm) goto die; /* invalid parameter */
	if (fm_read(&hdr,1,sizeof(pcx_header),fm) != 128) goto die; /* cant read header */

	/* check header */
	if (hdr.magic != 10) goto die;    /* not a pcx file */
	if (hdr.version != 5) goto die;   /* only version 5 is supported */
	if (hdr.encoding != 1) goto die;  /* only compressed images are supported */
	if (hdr.bits_per_pixel != 8 || (hdr.planes != 1 && hdr.planes != 3)) 
		goto die;                     /* only 256 color palette or 24 bit are supported */

	/* get relevant data from header. MAKE_INT() may look bulky, but all this stuff gets
	 * optimized into small funky bitshift stuff due to the common subexpressions. */
	xsize = MAKE_INT(hdr.xmax_hi,hdr.xmax_lo) - MAKE_INT(hdr.xmin_hi,hdr.xmin_lo) + 1;
	ysize = MAKE_INT(hdr.ymax_hi,hdr.ymax_lo) - MAKE_INT(hdr.ymin_hi,hdr.ymin_lo) + 1;
	bytes_per_line = MAKE_INT(hdr.bytes_per_line_hi,hdr.bytes_per_line_lo);
	scanline_size = hdr.planes * bytes_per_line;

	/* set up image */
	if (!(img=(rgb_image*)malloc(sizeof(rgb_image)))) goto die; /* cannot allocate memory */
	img->width = xsize;
	img->height = ysize;
	if (!(img->pixels = malloc(img->width * img->height * 3))) goto die; /* cannot allocate memory */

	/* header done, call aproperiate subfunction to read the image */
	if (hdr.planes == 1) {
		if (readpixels_256pal(fm,img,bytes_per_line) != 0) goto die;
	}
	else {
		if (readpixels_24bit(fm,img, bytes_per_line) != 0) goto die;
	}

	return img;

die:

	if (img) {
		if (img->pixels) free(img->pixels);
		free(img);
	}

	return 0;
}


int readpixels_256pal(struct fm_t* fm,rgb_image* img, int bytes_per_line)
{
	unsigned char* pal = 0;
	int subtotal, h, count, val=0;
	unsigned char* pix_pos = (unsigned char*)img->pixels;

	/* get palette */
	if (!(pal=(unsigned char*)malloc(256*3))) goto die; /* cannot allocate memory */
	if (fm_seek(fm,-(256*3+1),SEEK_END) == -1L) goto die; /* cannot seek to palette */
	fm_read(&val,1,1,fm);
	if (val != 12) goto die; /* cannot get palette magic */
	if (fm_read(pal,1,(3*256),fm) != (3*256)) goto die; /* cannot read palette data */
	if (fm_seek(fm,sizeof(pcx_header),SEEK_SET) == -1L) goto die; /* cannot seek back to image data */


	for (h = 0; h < img->height; h++) {
		int num_done = 0;
		for (subtotal = 0; subtotal < bytes_per_line; subtotal += count)
		{
			int i;

			/* this piece of code has as result that 'count' and 'val' are read in */
			fm_read(&val,1,1,fm);
//			if ((val=fgetc(fp))==EOF) goto die;

			if ((val & 0xc0) == 0xc0) {
				count = 0x3f & val;
				fm_read(&val,1,1,fm);
//				if ((val=fgetc(fp))==EOF) goto die;
			}
			else count = 1;

			/* do something with them */
			for (i = 0; i < count; i++)
				if (num_done < img->width) {
					register int tmp = val*3; /* optimize mul */
					pix_pos[0] = pal[tmp+0];
					pix_pos[1] = pal[tmp+1];
					pix_pos[2] = pal[tmp+2];
					pix_pos +=3;
					num_done++;
				}
		}
	}

	free(pal);
	return 0;

die:

	if (pal) free(pal);
	return 1;
}


int readpixels_24bit(struct fm_t* fm,rgb_image* img, int bytes_per_line)
{
	int i,j,count, val, nbytes = bytes_per_line * img->height * 3;
	unsigned char* pix = (unsigned char*)img->pixels;

	i = 0;								/* current plane in loop */
	j = 0;								/* bytes per line in loop */
	while (nbytes > 0)
	{
		/* this piece of code has as result that 'count' and 'val' are read in */
		fm_read(&val,1,1,fm);
//		if ((val=fgetc(fp))==EOF)	return 1;
		if ((val & 0xc0) == 0xc0) {
			count = 0x3f & val;
			fm_read(&val,1,1,fm);
//			if ((val=fgetc(fp))==EOF) 				return 1;
		}
		else count = 1;

		while (count-- > 0) {
			if (j < img->width) {
				*pix = val;
				pix += 3;
			}
			j++;
			nbytes--;
			if (j == bytes_per_line) {
				j = 0;
				if (++i < 3) {
					pix -= (img->width*3) - 1;
				}
				else {
					pix -= (3-1);
					i = 0;
				}
			}
		}
	}
	
	return 0;
}

